/*
 * scaner.c
 * Lexical analyzer for uPL0c.
 *
 * Copyright 2024 dh33ex <dh33ex@riseup.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA or visit <http://www.gnu.org/licenses/>.
 *
 *
 */

#include "scaner.h"
#include "gios.h"

#include <limits.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>

/* internal functions */
static void read_comment(void);
static void read_number(void);
static int read_ident(void);
static int read_token(void);

static void read_comment(void) {
    /* skip comment */
    int old_ch, ch;
    while (ch != EOF) {
        old_ch = ch;
        ch = get_ch();
        if (ch == ')' && old_ch == '*') {
            return;
        }
    }
    print_error("comment not terminated");
}


static int read_ident(void) {
    int ch;
    int i = 0;

    char id[ID_LEN];

    /* read identifier */
    do {
        if (i < ID_LEN-1) {
            id[i] = get_ch();
        } else {
            print_error("identifier to long");
        }
    } while (id[i] == '_' || isalnum(id[i++]));
    unget_ch(id[i-1]);
    id[i-1] = '\0';

    /* define a keyword and save the corresponding token */
    if (!strcmp(id, "DO")) return TOK_DO;
    else if (!strcmp(id, "IF")) return TOK_IF;
    else if (!strcmp(id, "ROT")) return TOK_ROT;
    else if (!strcmp(id, "END")) return TOK_END;
    else if (!strcmp(id, "VAR")) return TOK_VAR;
    else if (!strcmp(id, "MEM")) return TOK_MEM;
    else if (!strcmp(id, "ELSE")) return TOK_ELSE;
    else if (!strcmp(id, "THEN")) return TOK_THEN;
    else if (!strcmp(id, "BEGIN")) return TOK_BEGIN;
    else if (!strcmp(id, "CONST")) return TOK_CONST;
    else if (!strcmp(id, "WHILE")) return TOK_WHILE;
    else if (!strcmp(id, "PROCEDURE")) return TOK_PROCEDURE;
    else if (!strcmp(id, "INTEGER")) return TOK_INTEGER;
    else if (!strcmp(id, "CALL")) return TOK_CALL;
    else {
        strcpy(compiler.id, id);
        return TOK_IDENT;
    }
}


static void read_number(void) {
    int ch = get_ch();
    compiler.val = 0;

    do {
        /* 65535 - max 16-bit unsigned number */
        if (compiler.val <= (65535 - ch + '0')/10) {
            compiler.val = 10 * (compiler.val) + (ch - '0');
        } else {
            print_error("number to large");
            compiler.val = 0;
        }
        ch = get_ch();
    } while (isdigit(ch));

    unget_ch(ch);
}


int read_token(void) {
    int ch;

    /* skip spaces */
    do {
        ch = get_ch();
    } while (ch != EOF && ch <= ' ');

    /* identify the token and save the identifier/number, if available */
    switch (ch) {
        case EOF: return TOK_EOF; break;
        case '*': return TOK_TIMES; break;
        case '/': return TOK_DIV; break;
        case '&': return TOK_AND; break;
        case '|': return TOK_OR; break;
        case '^': return TOK_XOR; break;
        case '+': return TOK_PLUS; break;
        case '-': return TOK_MINUS; break;
        case '=': return TOK_EQL; break;
        case '#': return TOK_NEQ; break;
        case '<':
            ch = get_ch();
            if (ch == '=') {
                return TOK_LEQ;
            } else if (ch == '<') {
                 return TOK_LSL;
            } else {
                unget_ch(ch);
                return TOK_LSS;
            }
            break;

        case '>':
            ch = get_ch();
            if (ch == '=') {
                return TOK_GEQ;
            } else if (ch == '>') {
                 return TOK_ASR;
            }else {
                unget_ch(ch);
                return TOK_GTR;
            }
            break;

        case '.': return TOK_DOT; break;
        case ',': return TOK_COMMA; break;
        case ';': return TOK_SEMICOLON; break;
        case ':':
            ch = get_ch();
            if (ch == '=') {
                return TOK_BECOMES;
            } else {
                unget_ch(ch);
                return TOK_COLON;
            }
            break;

        case '(':
            ch = get_ch();
            if (ch == '*') {
                read_comment();
                return read_token();
            } else {
                unget_ch(ch);
                return TOK_LPAREN;
            }
            break;

        case ')': return TOK_RPAREN; break;
        case '~': return TOK_NOT; break;
        default:
            if (isalpha(ch) || ch == '_') {
                unget_ch(ch);
                return read_ident();
            } else if (isdigit(ch)) {
                unget_ch(ch);
                read_number();
                return TOK_NUMBER;
            } else {
                print_error("unsupported terminal character");
                return TOK_NULL;
            }
            break;
    }
}


void next_token(void) {
    compiler.token = read_token();
}
